﻿using System.Collections.Generic;

namespace GameToolkit.BehaviourTree
{
    internal struct CreationBuffer
    {
        internal int depth;
        internal Composite composite;
        internal BehaviourTreeObject instance;
        internal BehaviourTreeObject parent;
        // 创建过程中数组范围 [start,end) 记录了该节点关联的子节点和装饰节点
        internal int blockStart;
        internal int blockEnd;

        internal static CreationBuffer Get(Composite composite, int depth, BehaviourTreeObject parent = null)
        {
            if (composite == null)
                return default;
            if (depth > CreateBehaviourTreeContext.MAX_DEPTH)
                throw new System.StackOverflowException(string.Format(@"The behaviour tree was out of stack size ({0})
Create Type: {1}
Create GUID: {2}",
CreateBehaviourTreeContext.MAX_DEPTH,
composite.GetType().Name,
#if UNITY_EDITOR
composite._guid
#else
"-"
#endif
));
            if (composite is RegisterReference register)
                return register.Register == null ? default : Get(register.Register.Source, depth + 1, parent);
            var inst = composite.Create();
            if (inst == null)
                return default;
            CreationBuffer buf;
            buf.depth = depth;
            buf.composite = composite;
            buf.instance = inst;
            buf.parent = parent;
            buf.blockEnd = -1;
            buf.blockStart = -1;
            return buf;
        }

        internal static CreationBuffer Get(CompositeState compositeInput, int depth, BehaviourTreeObject parent)
        {
            if (parent == null || compositeInput.composite == null)
                return default;
            if (depth > CreateBehaviourTreeContext.MAX_DEPTH)
                throw new System.StackOverflowException(string.Format(@"The behaviour tree was out of stack size ({0})
Create Type: {1}
Create GUID: {2}",
CreateBehaviourTreeContext.MAX_DEPTH,
compositeInput.composite.GetType().Name,
#if UNITY_EDITOR
compositeInput.composite._guid
#else
"-"
#endif
));
            var com = compositeInput.composite;
            if (com is RegisterReference register)
                return register.Register == null ? default : Get(register.Register.Source, depth + 1, parent);
            var inst = com?.Create();
            if (inst == null)
                return default;
            if (compositeInput.state == NodeState.Failed)
                inst = new NotLogicInput(inst);
            CreationBuffer buf;
            buf.depth = depth;
            buf.composite = com;
            buf.instance = inst;
            buf.parent = parent;
            buf.blockEnd = -1;
            buf.blockStart = -1;
            return buf;
        }
    }

    internal class CreateBehaviourTreeContext
    {
        internal const int MAX_DEPTH = 512;

        internal readonly BehaviourTreePlayer player;

        internal BehaviourTreeObject parent;
        internal Composite composite;
        internal readonly IList<Decoration> decoratorBuffer;
        internal readonly IList<IInterruptionSource> interruptionBuffer;
        internal readonly IList<IExecutableUpdateCallback> updateBuffer;
        internal readonly IList<IExecutableEntryCallback> entryBuffer;
        internal readonly IList<ExecutableEntry> childrenBuffer;

        internal CreateBehaviourTreeContext(BehaviourTreePlayer player)
        {
            this.player = player;
            decoratorBuffer = new List<Decoration>();
            interruptionBuffer = new List<IInterruptionSource>();
            updateBuffer = new List<IExecutableUpdateCallback>();
            entryBuffer = new List<IExecutableEntryCallback>();
            childrenBuffer = new List<ExecutableEntry>();
        }

        internal ExecutableEntry Create(BehaviourTreeAsset asset)
        {
            var nodes = asset == null || asset.m_Nodes == null ? 0 : asset.m_Nodes.Length;
            var root = nodes > 0 && asset.m_RootIndex >= 0 && asset.m_RootIndex < nodes ? asset.m_Nodes[asset.m_RootIndex] : null;
            var creat = CreationBuffer.Get(root, 0);
            var rootEntry = creat.instance as ExecutableEntry;
            if(rootEntry == null)
                return null;
            var nodesInstance = new List<CreationBuffer>(nodes)
            {
                creat
            };
            var index = 0;
            // create all nodes
            while (index < nodesInstance.Count)
            {
                var i = index++;
                creat = nodesInstance[i];
                creat.blockStart = nodesInstance.Count;
                if (creat.composite is ExecutableComposite exec)
                {
                    // create children
                    var children = exec.m_Children;
                    var size = children == null ? 0 : children.Length;
                    for (int k = 0; k < size; k++)
                    {
                        var child = CreationBuffer.Get(children[k], creat.depth + 1, creat.instance);
                        if (child.instance is ExecutableEntry)
                        {
                            nodesInstance.Add(child);
                        }
                    }
                    // create decorations
                    var decors = exec.m_Decorations;
                    size = decors == null ? 0 : decors.Length;
                    for (int k = 0; k < size; k++)
                    {
                        var decor = CreationBuffer.Get(decors[k], 1, creat.instance);
                        if (decor.instance is Decoration)
                        {
                            nodesInstance.Add(decor);
                        }
                    }
                }
                else if (creat.composite is LogicWithInputComposite logic)
                {
                    var inputs = logic.inputs;
                    var size = inputs == null ? 0 : inputs.Length;
                    for (int k = 0; k < size; k++)
                    {
                        var cond = CreationBuffer.Get(inputs[k], creat.depth + 1, creat.instance);
                        if (cond.instance is Decoration)
                        {
                            nodesInstance.Add(cond);
                        }
                    }
                }
                creat.blockEnd = nodesInstance.Count;
                nodesInstance[i] = creat;
            }
            var buffer = new List<CreationBuffer>();
            // invoke initialize
            for (int i = 0; i < nodesInstance.Count; i++)
            {
                creat = nodesInstance[i];
                parent = creat.parent;
                composite = creat.composite;
                childrenBuffer.Clear();
                decoratorBuffer.Clear();
                updateBuffer.Clear();
                interruptionBuffer.Clear();
                entryBuffer.Clear();
                if(creat.instance is ExecutableEntry entry)
                {
                    var isActionNode = creat.instance is ActionNode;
                    // get self decoration callbacks
                    for (int k = creat.blockStart; k < creat.blockEnd; k++)
                    {
                        var child = nodesInstance[k];
                        if (child.instance is ExecutableEntry exec)
                        {
                            childrenBuffer.Add(exec);
                        }
                        else if (child.instance is Decoration decoration)
                        {
                            decoratorBuffer.Add(decoration);
                            if (child.instance is IInterruptionSource interrupt)
                                interruptionBuffer.Add(interrupt);
                            buffer.Clear();
                            buffer.Add(child);
                            index = 0;
                            while (index < buffer.Count)
                            {
                                var decor = buffer[index++];
                                var inst = decor.instance;
                                if (inst is IWrapCallback wrap)
                                    inst = wrap.wrapInstance;
                                if (inst is IExecutableEntryCallback call)
                                    entryBuffer.Add(call);
                                if (isActionNode && inst is IExecutableUpdateCallback ucall)
                                    updateBuffer.Add(ucall);
                                for (int n = decor.blockStart; n < decor.blockEnd; n++)
                                {
                                    var inputdecor = nodesInstance[n];
                                    if (inputdecor.instance is Decoration)
                                        buffer.Add(inputdecor);
                                }
                            }
                        }
                    }
                    // get parent update callbacks
                    var p = isActionNode ? parent as ExecutableEntry : null;
                    while (p != null && p is not ActionNode)
                    {
                        if (p is IExecutableUpdateCallback up)
                        {
                            updateBuffer.Add(up);
                        }
                        var size = p.DecoratorCount;
                        for (int k = 0; k < size; k++)
                        {
                            BehaviourTreeObject dec = p.GetDecorator(k);
                            if (dec is IWrapCallback wrap)
                                dec = wrap.wrapInstance;
                            if (dec is IExecutableUpdateCallback ucall)
                                updateBuffer.Add(ucall);
                            //var dec = p.GetDecorator(k) as IExecutableUpdateCallback;
                            //if (dec != null)
                            //    updateBuffer.Add(dec);
                        }
                        p = p.Parent;
                    }
                }
                else if (creat.instance is IInterruptionSource)
                {
                    for (int k = creat.blockStart; k < creat.blockEnd; k++)
                    {
                        var inst = nodesInstance[k].instance;
                        if (inst is IInterruptionSource interruption)
                            interruptionBuffer.Add(interruption);
                    }
                }
                creat.instance.InitializeSelf(this);
            }
            return rootEntry;
        }

    }

}
